package com.hww.spring;

import com.hww.annotation.*;
import com.hww.aware.*;
import com.hww.bean.*;
import com.hww.context.*;


import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class MyApplicationContext implements MyBeanFactory {
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, Object> singletonObjects = new HashMap<>();
    private List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();

    public MyApplicationContext(Class<?> configClass) {
        String basePackage = null;
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScan = configClass.getAnnotation(ComponentScan.class);
            basePackage = componentScan.value();
        } else {
            throw new RuntimeException("Config class must be annotated with @ComponentScan");
        }
        scan(basePackage);
        createBeanInstances();
        processAwareInterfaces();
        applyBeanPostProcessorsBeforeInitialization();
        invokePostConstructMethods();
        invokeInitializingBeanMethods();
        applyBeanPostProcessorsAfterInitialization();
    }


    private void scan(String basePackage) {
        String packagePath = basePackage.replace('.', '/');
        URL resource = Thread.currentThread().getContextClassLoader().getResource(packagePath);
        if (resource != null) {
            File dir = new File(resource.getFile());
            if (dir.exists()) {
                File[] files = dir.listFiles();
                if (files != null) {
                    for (File file : files) {
                        if (file.isDirectory()) {
                            scan(basePackage + "." + file.getName());
                        } else if (file.getName().endsWith(".class")) {
                            //com.hww.service.UserService
                            String className = basePackage + '.' + file.getName().replace(".class", "");
                            try {
                                Class<?> clazz = Class.forName(className);
                                if (isComponentClass(clazz)) {
                                    String beanName = getBeanNameFromAnnotation(clazz);
                                    BeanDefinition beanDefinition = new BeanDefinition(beanName, clazz);
                                    if (clazz.isAnnotationPresent(Scope.class)) {
                                        Scope scope = clazz.getAnnotation(Scope.class);
                                        beanDefinition.setScope(scope.value());
                                    }
                                    beanDefinitionMap.put(beanName, beanDefinition);
                                    if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                                        beanPostProcessors.add((BeanPostProcessor) clazz.newInstance());
                                    }
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }


    private void createBeanInstances() {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            // Only create non-lazy singleton beans during initialization
            if ("singleton".equals(beanDefinition.getScope())) {
                getBean(beanName); // This will trigger DCL creation
            }
        }
    }

    private Object createBean(BeanDefinition beanDefinition) throws Exception {
        Class<?> beanClass = beanDefinition.getBeanClass();
        Constructor<?> constructor = findSuitableConstructor(beanClass);
        return constructor.newInstance();
    }

    private Constructor<?> findSuitableConstructor(Class<?> beanClass) {
        Constructor<?>[] constructors = beanClass.getConstructors();
        if (constructors.length == 1) {
            return constructors[0];
        }
        for (Constructor<?> constructor : constructors) {
            if (constructor.isAnnotationPresent(Autowired.class)) {
                return constructor;
            }
        }
        return constructors[0];
    }

    private void injectDependencies(Object bean) throws IllegalAccessException {
        Class<?> beanClass = bean.getClass();
        Field[] fields = beanClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                String beanName = lowerFirstCase(field.getType().getSimpleName());
                Object dependency = singletonObjects.get(beanName);
                field.set(bean, dependency);
            }
        }
    }

    private void processAwareInterfaces() {
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
            String beanName = entry.getKey();
            Object bean = entry.getValue();
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }
            if (bean instanceof ApplicationContextAware) {
                ((ApplicationContextAware) bean).setApplicationContext(this);
            }
            if (bean instanceof BeanFactoryAware) {
                ((BeanFactoryAware) bean).setBeanFactory(this);
            }
        }
    }

    private void applyBeanPostProcessorsBeforeInitialization() {
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
            String beanName = entry.getKey();
            Object bean = entry.getValue();
            for (BeanPostProcessor processor : beanPostProcessors) {
                bean = processor.postProcessBeforeInitialization(bean, beanName);
            }
        }
    }

    private void invokePostConstructMethods() {
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
            Object bean = entry.getValue();
            Class<?> beanClass = bean.getClass();
            Method[] methods = beanClass.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(PostConstruct.class)) {
                    try {
                        method.invoke(bean);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    private void invokeInitializingBeanMethods() {
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
            Object bean = entry.getValue();
            if (bean instanceof InitializingBean) {
                try {
                    ((InitializingBean) bean).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void applyBeanPostProcessorsAfterInitialization() {
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
            String beanName = entry.getKey();
            Object bean = entry.getValue();
            for (BeanPostProcessor processor : beanPostProcessors) {
                bean = processor.postProcessAfterInitialization(bean, beanName);
            }
        }
    }

    public void destroyBeans() {
        for (Map.Entry<String, Object> entry : singletonObjects.entrySet()) {
            Object bean = entry.getValue();
            if (bean instanceof DisposableBean) {
                try {
                    ((DisposableBean) bean).destroy();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            Class<?> beanClass = bean.getClass();
            Method[] methods = beanClass.getMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(PreDestroy.class)) {
                    try {
                        method.invoke(bean);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    @Override
    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (beanDefinition == null) {
            throw new RuntimeException("No bean found with name: " + beanName);
        }
        if ("singleton".equals(beanDefinition.getScope())) {
            Object singletonBean = singletonObjects.get(beanName);
            if (singletonBean == null) {
                synchronized (this) {
                    singletonBean = singletonObjects.get(beanName);
                    if (singletonBean == null) {
                        singletonBean = createBeanInstance(beanDefinition);
                        singletonObjects.put(beanName, singletonBean);
                    }
                }
            }
            return singletonBean;
        } else {
            // For prototype scope, always create new instance
            return createBeanInstance(beanDefinition);
        }
    }

    private Object createBeanInstance(BeanDefinition beanDefinition) {
        try {
            // 1. 实例化 - 对象实例化
            Object bean = createBean(beanDefinition);
            
            // 2. 属性注入
            populateProperties(bean);
            
            // 3. 处理Aware接口
            processAwareInterfaces(bean, beanDefinition.getBeanName());
            
            // 4. BeanPostProcessor前置处理
            bean = applyBeanPostProcessorsBeforeInitialization(bean, beanDefinition.getBeanName());
            
            // 5. @PostConstruct注解方法
            invokePostConstructMethod(bean);
            
            // 6. InitializingBean接口方法
            if (bean instanceof InitializingBean) {
                try {
                    ((InitializingBean) bean).afterPropertiesSet();
                } catch (Exception e) {
                    throw new RuntimeException("Failed to initialize bean", e);
                }
            }
            
            // 7. BeanPostProcessor后置处理
            bean = applyBeanPostProcessorsAfterInitialization(bean, beanDefinition.getBeanName());
            
            return bean;
        } catch (Exception e) {
            throw new RuntimeException("Failed to create bean instance", e);
        }
    }

    private void populateProperties(Object bean) throws IllegalAccessException {
        Class<?> beanClass = bean.getClass();
        // 处理@Autowired注解的属性注入
        for (Field field : beanClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                String dependencyBeanName = lowerFirstCase(field.getType().getSimpleName());
                Object dependency = getBean(dependencyBeanName); // 使用getBean确保依赖也被正确初始化
                field.set(bean, dependency);
            }
        }
        
        // 处理setter方法注入
        for (Method method : beanClass.getMethods()) {
            if (method.isAnnotationPresent(Autowired.class) && method.getName().startsWith("set") && method.getParameterCount() == 1) {
                try {
                    String propertyName = lowerFirstCase(method.getName().substring(3));
                    Class<?> paramType = method.getParameterTypes()[0];
                    String dependencyBeanName = lowerFirstCase(paramType.getSimpleName());
                    Object dependency = getBean(dependencyBeanName);
                    method.invoke(bean, dependency);
                } catch (Exception e) {
                    throw new RuntimeException("Failed to inject dependency through setter", e);
                }
            }
        }
    }

    private void processAwareInterfaces(Object bean, String beanName) {
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(beanName);
        }
        if (bean instanceof ApplicationContextAware) {
            ((ApplicationContextAware) bean).setApplicationContext(this);
        }
        if (bean instanceof BeanFactoryAware) {
            ((BeanFactoryAware) bean).setBeanFactory(this);
        }
    }

    private Object applyBeanPostProcessorsBeforeInitialization(Object bean, String beanName) {
        Object result = bean;
        for (BeanPostProcessor processor : beanPostProcessors) {
            Object current = processor.postProcessBeforeInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    private Object applyBeanPostProcessorsAfterInitialization(Object bean, String beanName) {
        Object result = bean;
        for (BeanPostProcessor processor : beanPostProcessors) {
            Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {
                return result;
            }
            result = current;
        }
        return result;
    }

    private void invokePostConstructMethod(Object bean) {
        Class<?> beanClass = bean.getClass();
        Method[] methods = beanClass.getMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(PostConstruct.class)) {
                try {
                    method.invoke(bean);
                } catch (Exception e) {
                    throw new RuntimeException("Failed to invoke @PostConstruct method", e);
                }
            }
        }
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public Map<String, BeanDefinition> getBeanDefinitionMap() {
        return beanDefinitionMap;
    }

    private String lowerFirstCase(String str) {
        if (str == null || str.length() == 0) {
            return str;
        }
        return Character.toLowerCase(str.charAt(0)) + str.substring(1);
    }


    /**
     * 从注解中获取 bean 的名称
     * 按 Component、Service、Repository、Controller 的顺序检查注解的 value 属性
     * 如果属性不为空则使用该值，否则使用类名首字母小写
     *
     * @param clazz 类对象
     * @return bean 的名称
     */
    private String getBeanNameFromAnnotation(Class<?> clazz) {
        // Define an array of annotation types with specific types
        Class<? extends java.lang.annotation.Annotation>[] annotationClasses = new Class[]{
                Component.class, Service.class, Repository.class, Controller.class
        };
        for (Class<? extends java.lang.annotation.Annotation> annotationClass : annotationClasses) {
            // Get the annotation instance
            java.lang.annotation.Annotation annotation = clazz.getAnnotation(annotationClass);
            if (annotation != null) {
                try {
                    // Reflectively get the value method of the annotation
                    Method valueMethod = annotationClass.getMethod("value");
                    String value = (String) valueMethod.invoke(annotation);
                    if (value != null && !value.isEmpty()) {
                        return value;
                    }
                } catch (Exception e) {
                    // Ignore exceptions and continue checking the next annotation
                }
            }
        }
        return lowerFirstCase(clazz.getSimpleName());
    }

    /**
     * 检查类是否带有 Component、Service、Repository 或 Controller 注解
     *
     * @param clazz 要检查的类
     * @return 如果带有任意一个注解则返回 true，否则返回 false
     */
    private boolean isComponentClass(Class<?> clazz) {
        return clazz.isAnnotationPresent(Component.class) ||
                clazz.isAnnotationPresent(Service.class) ||
                clazz.isAnnotationPresent(Repository.class) ||
                clazz.isAnnotationPresent(Controller.class);
    }


}